/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.engine.impl.bpmn.behavior;

import com.je.bpm.core.model.FlowElement;
import com.je.bpm.core.model.SequenceFlow;
import com.je.bpm.core.model.event.IntermediateCatchEvent;
import com.je.bpm.core.model.gateway.EventGateway;
import com.je.bpm.engine.delegate.DelegateExecution;
import com.je.bpm.engine.history.DeleteReason;
import com.je.bpm.engine.impl.context.Context;
import com.je.bpm.engine.impl.interceptor.CommandContext;
import com.je.bpm.engine.impl.persistence.entity.ExecutionEntity;
import com.je.bpm.engine.impl.persistence.entity.ExecutionEntityManager;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class IntermediateCatchEventActivityBehavior extends AbstractBpmnActivityBehavior {

    private static final long serialVersionUID = 1L;

    @Override
    public void execute(DelegateExecution execution) {
        // Do nothing: waitstate behavior
    }

    @Override
    public void trigger(DelegateExecution execution, String signalName, Object signalData) {
        leaveIntermediateCatchEvent(execution);
    }

    /**
     * Specific leave method for intermediate events: does a normal leave(), except
     * when behind an event based gateway. In that case, the other events are cancelled
     * (we're only supporting the exclusive event based gateway type currently).
     * and the process instance is continued through the triggered event.
     */
    public void leaveIntermediateCatchEvent(DelegateExecution execution) {
        EventGateway eventGateway = getPrecedingEventBasedGateway(execution);
        if (eventGateway != null) {
            deleteOtherEventsRelatedToEventBasedGateway(execution, eventGateway);
        }

        leave(execution); // Normal leave
    }

    /**
     * Should be subclassed by the more specific types.
     * For an intermediate catch without type, it's simply leaving the event.
     */
    public void eventCancelledByEventGateway(DelegateExecution execution) {
        Context.getCommandContext().getExecutionEntityManager().deleteExecutionAndRelatedData((ExecutionEntity) execution,
                DeleteReason.EVENT_BASED_GATEWAY_CANCEL);
    }

    protected EventGateway getPrecedingEventBasedGateway(DelegateExecution execution) {
        FlowElement currentFlowElement = execution.getCurrentFlowElement();
        if (currentFlowElement instanceof IntermediateCatchEvent) {
            IntermediateCatchEvent intermediateCatchEvent = (IntermediateCatchEvent) currentFlowElement;
            List<SequenceFlow> incomingSequenceFlow = intermediateCatchEvent.getIncomingFlows();

            // If behind an event based gateway, there is only one incoming sequence flow that originates from said gateway
            if (incomingSequenceFlow != null && incomingSequenceFlow.size() == 1) {
                SequenceFlow sequenceFlow = incomingSequenceFlow.get(0);
                FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
                if (sourceFlowElement instanceof EventGateway) {
                    return (EventGateway) sourceFlowElement;
                }
            }

        }
        return null;
    }

    protected void deleteOtherEventsRelatedToEventBasedGateway(DelegateExecution execution, EventGateway eventGateway) {

        // To clean up the other events behind the event based gateway, we must gather the
        // activity ids of said events and check the _sibling_ executions of the incoming execution.
        // Note that it can happen that there are multiple such execution in those activity ids,
        // (for example a parallel gw going twice to the event based gateway, kinda silly, but valid)
        // so we only take _one_ result of such a query for deletion.

        // Gather all activity ids for the events after the event based gateway that need to be destroyed
        List<SequenceFlow> outgoingSequenceFlows = eventGateway.getOutgoingFlows();
        Set<String> eventActivityIds = new HashSet<String>(outgoingSequenceFlows.size() - 1); // -1, the event being triggered does not need to be deleted
        for (SequenceFlow outgoingSequenceFlow : outgoingSequenceFlows) {
            if (outgoingSequenceFlow.getTargetFlowElement() != null
                    && !outgoingSequenceFlow.getTargetFlowElement().getId().equals(execution.getCurrentActivityId())) {
                eventActivityIds.add(outgoingSequenceFlow.getTargetFlowElement().getId());
            }
        }

        CommandContext commandContext = Context.getCommandContext();
        ExecutionEntityManager executionEntityManager = commandContext.getExecutionEntityManager();

        // Find the executions
        List<ExecutionEntity> executionEntities = executionEntityManager
                .findExecutionsByParentExecutionAndActivityIds(execution.getParentId(), eventActivityIds);

        // Execute the cancel behaviour of the IntermediateCatchEvent
        for (ExecutionEntity executionEntity : executionEntities) {
            if (eventActivityIds.contains(executionEntity.getActivityId()) && execution.getCurrentFlowElement() instanceof IntermediateCatchEvent) {
                IntermediateCatchEvent intermediateCatchEvent = (IntermediateCatchEvent) execution.getCurrentFlowElement();
                if (intermediateCatchEvent.getBehavior() instanceof IntermediateCatchEventActivityBehavior) {
                    ((IntermediateCatchEventActivityBehavior) intermediateCatchEvent.getBehavior()).eventCancelledByEventGateway(executionEntity);
                    eventActivityIds.remove(executionEntity.getActivityId()); // We only need to delete ONE execution at the event.
                }
            }
        }
    }

}
